package kha.netsync;

import haxe.macro.Context;
import haxe.macro.Expr.Field;

class ControllerBuilder {
	public static var nextId: Int = 0;

	macro static public function build(): Array<Field> {
		var fields = Context.getBuildFields();

		#if (!kha_server && (kha_html5 || kha_kore))
		{
			var funcindex = 0;
			for (field in fields) {
				var input = false;
				for (meta in field.meta) {
					if (meta.name == "input") {
						input = true;
						break;
					}
				}
				if (!input)
					continue;

				switch (field.kind) {
					case FFun(f):
						var size = 4;
						for (arg in f.args) {
							switch (arg.type) {
								case TPath(p):
									switch (p.name) {
										case "Int":
											size += 4;
										case "String":
											size += 1;
										case "Float":
											size += 8;
										case "Bool":
											size += 1;
										case "KeyCode":
											size += 1;
									}
								default:
							}
						}

						var expr = macro @:mergeBlock {
							var bytes = haxe.io.Bytes.alloc($v{size});
							bytes.setInt32(0, $v{funcindex});
						};
						var index: Int = 4;
						for (arg in f.args) {
							switch (arg.type) {
								case TPath(p):
									switch (p.name) {
										case "Int":
											var argname = arg.name;
											expr = macro @:mergeBlock {
												$expr;
												bytes.setInt32($v{index}, $i{argname});
											};
											index += 4;
										case "String":
											var argname = arg.name;
											expr = macro @:mergeBlock {
												$expr;
												bytes.set($v{index}, $i{argname}.charCodeAt(0));
											};
											index += 1;
										case "Float":
											var argname = arg.name;
											expr = macro @:mergeBlock {
												$expr;
												bytes.setDouble($v{index}, $i{argname});
											};
											index += 8;
										case "Bool":
											var argname = arg.name;
											expr = macro @:mergeBlock {
												$expr;
												bytes.set($v{index}, $i{argname} ?1:0);
											};
											index += 1;
										case "KeyCode":
											var argname = arg.name;
											expr = macro @:mergeBlock {
												$expr;
												bytes.set($v{index}, $i{argname});
											};
											index += 1;
									}
								default:
							}
						}
						var original = f.expr;
						expr = macro {
							if (kha.netsync.Session.the() != null) {
								$expr;
								kha.netsync.Session.the().sendControllerUpdate(_id(), bytes);
							}
							$original;
						};
						f.expr = expr;
					default:
				}
				++funcindex;
			}
		}
		#end

		// macros failing everywhere but in JavaScript?
		#if (kha_server || kha_html5 || kha_kore)
		var receive = macro @:mergeBlock {
			var funcindex = bytes.getInt32(0);
		};
		{
			var funcindex = 0;
			for (field in fields) {
				var input = false;
				for (meta in field.meta) {
					if (meta.name == "input") {
						input = true;
						break;
					}
				}
				if (!input)
					continue;

				switch (field.kind) {
					case FFun(f):
						var expr = macro {};
						var index: Int = 4;
						var varindex: Int = 0;
						for (arg in f.args) {
							switch (arg.type) {
								case TPath(p):
									switch (p.name) {
										case "Int":
											var argname = arg.name;
											var varname = "input" + varindex;
											expr = macro @:mergeBlock {
												$expr;
												var $varname:Int = bytes.getInt32($v{index});
											};
											index += 4;
										case "String":
											var argname = arg.name;
											var varname = "input" + varindex;
											expr = macro @:mergeBlock {
												$expr;
												var $varname:String = String.fromCharCode(bytes.get($v{index}));
											};
											index += 1;
										case "Float":
											var argname = arg.name;
											var varname = "input" + varindex;
											expr = macro @:mergeBlock {
												$expr;
												var $varname:Float = bytes.getDouble($v{index});
											};
											index += 8;
										case "Bool":
											var argname = arg.name;
											var varname = "input" + varindex;
											expr = macro @:mergeBlock {
												$expr;
												var $varname:Bool = bytes.get($v{index}) != 0;
											};
											index += 1;
										case "KeyCode":
											var argname = arg.name;
											var varname = "input" + varindex;
											expr = macro @:mergeBlock {
												$expr;
												var $varname:kha.input.KeyCode = cast bytes.get($v{index});
											};
											index += 1;
									}
								default:
							}
							++varindex;
						}
						switch (varindex) {
							case 1:
								var funcname = field.name;
								receive = macro @:mergeBlock {
									$receive;
									if (funcindex == $v{funcindex}) {
										$expr;
										$i{funcname}(input0);
										return;
									}
								};
							case 2:
								var funcname = field.name;
								receive = macro @:mergeBlock {
									$receive;
									if (funcindex == $v{funcindex}) {
										$expr;
										$i{funcname}(input0, input1);
										return;
									}
								};
							case 3:
								var funcname = field.name;
								receive = macro @:mergeBlock {
									$receive;
									if (funcindex == $v{funcindex}) {
										$expr;
										$i{funcname}(input0, input1, input2);
										return;
									}
								};
							case 4:
								var funcname = field.name;
								receive = macro @:mergeBlock {
									$receive;
									if (funcindex == $v{funcindex}) {
										$expr;
										$i{funcname}(input0, input1, input2, input3);
										return;
									}
								};
							case 5:
								var funcname = field.name;
								receive = macro @:mergeBlock {
									$receive;
									if (funcindex == $v{funcindex}) {
										$expr;
										$i{funcname}(input0, input1, input2, input3, input4);
										return;
									}
								};
						}
					default:
				}
				++funcindex;
			}
		}

		fields.push({
			name: "_receive",
			doc: null,
			meta: [],
			access: [APublic, AOverride],
			kind: FFun({
				ret: null,
				params: null,
				expr: receive,
				args: [
					{
						value: null,
						type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
						opt: null,
						name: "bytes"
					}
				]
			}),
			pos: Context.currentPos()
		});
		#else
		fields.push({
			name: "_receive",
			doc: null,
			meta: [],
			access: [APublic, AOverride],
			kind: FFun({
				ret: null,
				params: null,
				expr: macro {},
				args: [
					{
						value: null,
						type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
						opt: null,
						name: "bytes"
					}
				]
			}),
			pos: Context.currentPos()
		});
		#end

		return fields;
	}
}
